﻿using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;

namespace ipdatacloud
{
    public class IPSearch
    {
        //private static readonly Lazy<IPSearch> lazy = new Lazy<IPSearch>(() => new IPSearch());
        //public static IPSearch Instance { get { return lazy.Value; } }

        private readonly string _datPath;
        private long[,] _prefMap;
        private byte[] _data;
        private readonly string _readMode;
        private readonly int _version;
        private FileStream _file;
        private long _numbers;

        private IPSearch(string filePath, string readMode, int version)
        {
            _datPath = filePath;
            _version = version;
            _readMode = readMode;
            if (version == 4)
            {
                LoadDat();
            }
            else if (version == 6)
            {
                LoadDatV6();
            }
            else
            {
                throw new Exception("Unsupported version");
            }
        }

        public static IPSearch Ipv4InstanceFileLocation(string filePath)
        {
            return new IPSearch(filePath, "file", 4);
        }

        public static IPSearch Ipv4InstanceMemoryLocation(string filePath)
        {
            return new Lazy<IPSearch>(() => new IPSearch(filePath, "mem", 4)).Value;
        }


        public static IPSearch Ipv4InstanceFileScenes(string filePath)
        {
            return new IPSearch(filePath, "mem", 4);
        }

        public static IPSearch Ipv4InstanceMemoryScenes(string filePath)
        {
            return new Lazy<IPSearch>(() => new IPSearch(filePath, "file", 4)).Value;
        }


        public static IPSearch Ipv4InstanceFileProxy(string filePath)
        {
            return new IPSearch(filePath, "mem", 4);
        }

        public static IPSearch Ipv4InstanceMemoryProxy(string filePath)
        {
            return new Lazy<IPSearch>(() => new IPSearch(filePath, "file", 4)).Value;
        }

        public static IPSearch Ipv6InstanceFile(string filePath)
        {
            return new IPSearch(filePath, "file", 6);
        }

        public static IPSearch Ipv6InstanceMemory(string filePath)
        {
            return new Lazy<IPSearch>(() => new IPSearch(filePath, "mem", 6)).Value;
        }


        /// <summary>
        /// 初始化二进制  ipdatacloud.dat 数据
        /// </summary>
        private void LoadDat()
        {
            _file = new FileStream(_datPath, FileMode.Open, FileAccess.Read);
            if (_readMode == "mem")
            {
                _data = File.ReadAllBytes(_datPath);
                _file.Close();
            }

            _prefMap = new long[256, 2];
            for (int k = 0; k < 256; k++)
            {
                int i = k * 8 + 4;
                long startIndex = UnpackInt4Byte(i);
                long endIndex = UnpackInt4Byte(i + 4);
                _prefMap[k, 0] = startIndex;
                _prefMap[k, 1] = endIndex;
            }
        }

        private void LoadDatV6()
        {
            _file = new FileStream(_datPath, FileMode.Open, FileAccess.Read);
            if (_readMode == "mem")
            {
                _data = File.ReadAllBytes(_datPath);
                _file.Close();
            }

            _numbers = UnpackInt4Byte(4);
            _prefMap = new long[65600, 2];

            for (int k = 0; k < _numbers; k++)
            {
                int i = k * 12 + 4 + 4;
                long startIndex = UnpackInt4Byte(i);
                long endIndex = UnpackInt4Byte(i + 4);
                long key = UnpackInt4Byte(i + 8);
                _prefMap[key, 0] = startIndex;
                _prefMap[key, 1] = endIndex;
            }
        }


        public string Find(string ip)
        {
            if (_version == 4)
            {
                (long val, long pref) = IpToLong(ip);
                long low = _prefMap[pref, 0], high = _prefMap[pref, 1];
                long cur = low == high ? low : BinarySearch(low, high, val);
                if (cur == 100000000)
                {
                    return "|";
                }

                return cur > -1 ? GetAddr(cur) : "||||||||||";
            }
            else if (_version == 6)
            {
                string[] ips = ip.Split(":");
                var addressBytes = IPAddress.Parse(ip).GetAddressBytes();
                BigInteger val = new BigInteger(addressBytes.Reverse().ToArray());
                long pref = Convert.ToInt64(ips[0], 16);
                long low = _prefMap[pref, 0], high = _prefMap[pref, 1];
                long cur = low == high ? low : BinarySearchV6(low, high, val);
                if (cur == 100000000)
                {
                    return "|";
                }

                return cur > -1 ? GetAddrV6(cur) : "||||||||||";
            }
            else
            {
                throw new Exception("unsupported IP version");
            }
        }


        private long BinarySearch(long low, long high, long k)
        {
            long M = 0, mid = 0;
            while (low <= high)
            {
                mid = (low + high) / 2;
                int p = (int)(2052 + (mid * 9));
                long endipnum = UnpackInt4Byte(p);
                if (endipnum >= k)
                {
                    M = mid;
                    if (mid == 0)
                    {
                        break;
                    }

                    high = mid - 1;
                }
                else
                    low = mid + 1;
            }

            return M;
        }

        private long BinarySearchV6(long low, long high, BigInteger k)
        {
            long M = 0;
            byte[] record = new byte[50];
            while (low <= high)
            {
                int mid = (int)(low + high) / 2;
                int p = (int)_numbers * 12 + 4 + 4 + (mid * 55);
                BigInteger endipNum;
                if (_readMode == "file")
                {
                    _file.Seek(p, SeekOrigin.Begin);
                    _file.Read(record, 0, record.Length);
                    endipNum = BigInteger.Parse(Encoding.UTF8.GetString(record).Replace("*", ""));
                }
                else if (_readMode == "mem")
                {
                    if (!BigInteger.TryParse(Encoding.UTF8.GetString(_data, p, 50).Replace("*", ""), out endipNum))
                    {
                        throw new Exception("conversion to biginteger failed");
                    }
                }
                else
                {
                    throw new Exception("unsupported reading method");
                }

                if (endipNum.CompareTo(k) >= 0)
                {
                    M = mid;
                    if (mid == 0)
                    {
                        break;
                    }

                    high = mid - 1;
                }
                else
                    low = mid + 1;
            }

            return M;
        }

        private string GetAddr(long cur)
        {
            long p = 2052 + (cur * 9);
            int offset = (int)UnpackInt4Byte((int)p + 4);
            int length = UnpackInt1Byte((int)p + 8);
            if (_readMode == "mem")
            {
                return Encoding.UTF8.GetString(_data, offset, length);
            }
            else if (_readMode == "file")
            {
                byte[] record = new byte[length];
                _file.Seek(offset, SeekOrigin.Begin);
                _file.Read(record, 0, length);
                return Encoding.UTF8.GetString(record);
            }
            else
            {
                throw new Exception("unsupported reading method");
            }
        }

        private string GetAddrV6(long cur)
        {
            int p = (int)(_numbers * 12 + 4 + 4 + (cur * 55));
            int offset = (int)UnpackInt4Byte(p + 50);
            int length = UnpackInt1Byte(p + 50 + 4);
            if (_readMode == "mem")
            {
                return Encoding.UTF8.GetString(_data, offset, length);
            }
            else if (_readMode == "file")
            {
                byte[] record = new byte[length];
                _file.Seek(offset, SeekOrigin.Begin);
                _file.Read(record, 0, length);
                return Encoding.UTF8.GetString(record);
            }
            else
            {
                throw new Exception("unsupported reading method");
            }
        }


        private (long ipLong, long prefix) IpToLong(string ipString)
        {
            // 尝试将 IP 字符串转换为 IPAddress 对象
            if (IPAddress.TryParse(ipString, out IPAddress ipAddress))
            {
                if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) // IPv4
                {
                    byte[] bytes = ipAddress.GetAddressBytes();
                    long ipLong = (long)(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]);
                    return (ipLong, bytes[0]);
                }

            }

            throw new FormatException("invalid IP address format");
        }

        private uint ReadBigEndian32(byte a, byte b, byte c, byte d)
        {
            return (uint)((a << 24) | (b << 16) | (c << 8) | d);
        }


        private uint ReadLittleEndian32(byte a, byte b, byte c, byte d)
        {
            return (uint)(a | (b << 8) | (c << 16) | (d << 24));
        }

        private long UnpackInt4Byte(int offset)
        {
            if (_readMode == "mem")
            {
                return (uint)((_data[offset]) | (_data[offset + 1] << 8) | (_data[offset + 2] << 16) |
                              ((_data[offset + 3] << 24)));
            }
            else if (_readMode == "file")
            {
                byte[] record = new byte[4];
                _file.Seek(offset, SeekOrigin.Begin);
                _file.Read(record, 0, record.Length);
                return (record[0] | (record[1] << 8) | (record[2] << 16) | (record[3] << 24));
            }
            else
            {
                throw new Exception("unsupported reading method");
            }
        }

        private int UnpackInt1Byte(int offset)
        {
            if (_readMode == "mem")
            {
                return (_data[offset]);
            }
            else if (_readMode == "file")
            {
                byte[] record = new byte[1];
                _file.Seek(offset, SeekOrigin.Begin);
                _file.Read(record, 0, record.Length);
                return (record[0]);
            }
            else
            {
                throw new Exception("unsupported reading method");
            }
        }


        public static void DownloadFile(string url, string localPath)
        {
            // 下载文件
            (string savePath, string err) = Download(url, localPath);
            if (!err.Equals(""))
            {
                throw new IOException(err);
            }

            // 检查文件类型并解压缩
            if (IsZipFile(savePath))
            {
                Console.WriteLine("the file is a ZIP archive. extracting...");
                ExtractZipFile(savePath, localPath);
                Console.WriteLine("Extraction completed");
                try
                {
                    if (File.Exists(savePath))
                    {
                        File.Delete(savePath);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error deleting file: " + ex.Message);
                }
            }
            else
            {
                Console.WriteLine("the file is not a ZIP archive");
            }
        }

        private static (string, string) Download(string url, string destinationPath)
        {
            using (HttpClient client = new HttpClient())
            {
                try
                {
                    HttpResponseMessage response =
                        client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result;
                    if (response.StatusCode == HttpStatusCode.Found)
                    {
                        string realUrl = response.Headers.Location.ToString();
                        if (!realUrl.Equals(""))
                        {
                            string realPath = realUrl.Split('?').First();
                            string fileName = realPath.Split('/').Last();
                            string savePath = Path.Combine(destinationPath, fileName);
                            HttpResponseMessage resp =
                                client.GetAsync(realUrl, HttpCompletionOption.ResponseHeadersRead).Result;
                            if (resp.StatusCode == HttpStatusCode.OK)
                            {
                                using (FileStream fs = new FileStream(savePath, FileMode.Create, FileAccess.Write,
                                           FileShare.None))
                                {
                                    resp.Content.ReadAsStreamAsync().Result.CopyTo(fs);
                                }

                                return (savePath, "");
                            }

                            return ("", "error in redirecting address request");
                        }

                        Console.WriteLine("the redirect address is empty, unable to complete the download");
                        return ("", "the redirect address is empty, unable to complete the download");
                    }

                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        string cd = response.Content.Headers.ContentDisposition.ToString();
                        if (!cd.Equals(""))
                        {
                            string fileName = cd.Split("=")[-1];
                            string savePath = Path.Combine(destinationPath, fileName);
                            using (FileStream fs = new FileStream(savePath, FileMode.Create, FileAccess.Write,
                                       FileShare.None))
                            {
                                response.Content.ReadAsStreamAsync().Result.CopyTo(fs);
                            }

                            return (savePath, "");
                        }

                        return ("", "the file download information is empty");
                    }

                    return ("", "request file error");
                }
                catch (TaskCanceledException ex)
                {
                    Console.WriteLine("The request was canceled: " + ex.Message);
                    return ("", ex.Message);
                }
                catch (HttpRequestException ex)
                {
                    Console.WriteLine("Request error: " + ex.Message);
                    return ("", ex.Message);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("An unexpected error occurred: " + ex.Message);
                    return ("", ex.Message);
                }
            }
        }

        static bool IsZipFile(string filePath)
        {
            try
            {
                using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                {
                    using (BinaryReader reader = new BinaryReader(fs))
                    {
                        // 检查文件的前4个字节是否为ZIP文件的魔术字节
                        uint magicNumber = reader.ReadUInt32();
                        return magicNumber == 0x04034b50;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error checking file type: " + ex.Message);
                return false;
            }
        }


        static void ExtractZipFile(string zipFilePath, string extractPath)
        {
            try
            {
                using (ZipArchive archive = ZipFile.OpenRead(zipFilePath))
                {
                    foreach (ZipArchiveEntry entry in archive.Entries)
                    {
                        string destinationPath = Path.Combine(extractPath, entry.FullName);

                        // 确保目标目录存在
                        string directoryPath = Path.GetDirectoryName(destinationPath);
                        if (!Directory.Exists(directoryPath))
                        {
                            Directory.CreateDirectory(directoryPath);
                        }

                        // 如果文件存在，则先删除
                        if (File.Exists(destinationPath))
                        {
                            File.Delete(destinationPath);
                        }

                        // 解压文件
                        entry.ExtractToFile(destinationPath);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error extracting ZIP file: " + ex.Message);
            }
        }
    }
}